#include "threads.h"
#include "commands.h"
#include "users.h"
#include "critical.h"

void thread_kill_all (void){
	pthread_kill (th_admin, SIGKILL);
	pthread_kill (th_pinger, SIGKILL);
	pthread_kill (th_server, SIGKILL);
}

void thread_kill (pthread_t th)
{
	pthread_kill (th, SIGKILL);
}

int thread_create (pthread_t * thread, pthread_attr_t * attr, void * (*start_routine)(void *), void * arg)
{
 int gard;

	gard = pthread_create (thread, attr, start_routine, arg);
		if (gard != 0)
		{
			printf ("error create thread\n");/*<--------------------------------------*/
			return -1;
		}

	pthread_mutex_lock (&ThreadsCounter_mutex);
	ThreadsCounter ++;
	pthread_mutex_unlock (&ThreadsCounter_mutex);

 return 0;
}

Thread* thread_new (pthread_t thid)
{
 int res;
 Thread* thread = malloc (sizeof (*thread));
	if (thread != NULL)
	{
		thread->thid = thid;
		thread->MaxFds = 0;
		thread->MaxClients = ServerConf.MaxClients / ServerConf.PollThreads;
		res = MUTEX_INIT(thread->ids_mutex);
		if (res != 0){
			fprintf (stderr, "mutex init error\n");
			free (thread);
			thread = NULL;
		}
		thread->ids_list = List_New();
		if (thread->ids_list == NULL)
		{
			MUTEX_CLEAR(thread->ids_mutex);
			free(thread);
			thread = NULL;
		}
	}
 return thread;
}

int thread_create_all (void)
{
 int gard,
	 i;
 pthread_t th; 

	gard = thread_create (&th_pinger, NULL, thread_func_ping, NULL);
			if (gard != 0){
				printf ("couldn't create the server thread\n");
				return -1;
			}

	gard = thread_create (&th_admin, NULL, thread_func_admin, NULL);
			if (gard != 0){
				printf ("couldn't create the server thread\n");
				return -1;
			}
		
	for (i=0; i<ServerConf.PollThreads; i++){
		gard = thread_create (&th, NULL, thread_func_poll, NULL);
			if (gard != 0){
				printf ("couldn't create the server thread\n");
				return -1;
			}
	}

	for (i=0; i<ServerConf.AuthThreads; i++){
		gard = thread_create (&th, NULL, thread_func_auth, NULL);
			if (gard != 0){
				printf ("couldn't create the server thread\n");
				return -1;
			}
	}

 return 0;
}

void thread_exit (void* retval)
{
	MUTEX_LOCK (ThreadsCounter_mutex);
	ThreadsCounter --;
	MUTEX_UNLOCK (ThreadsCounter_mutex);

	pthread_exit (retval);
}

void* thread_func_admin (void* arg){


	thread_exit (arg);

 return NULL;
}

void* thread_func_poll (void* arg)
/*
	
*/
{
	char buffer[BUFFERSIZE+1];
	int error;
	fd_set readset;
	Thread *thread = NULL;
	Message msg;
	Client* id = NULL;
	Data *temp = NULL;

	struct timeval tm;
	/*select on all sockets*/

	thread = thread_new (pthread_self());
	if (thread != NULL)
		List_PushTail (threads_list, thread);
	else{
		printf ("Error creating a new thread\n");
		pthread_exit ((void*)-1);
	}

	while (1){

		FD_ZERO (&readset);

		MUTEX_LOCK (thread->ids_mutex);
			id = List_InitIter (thread->ids_list);
			while (id != NULL){
				if (id->Socket.Socket == -1)
				{
					client_del (id->user);
					id = NULL;
					temp = thread->ids_list->iter;
					id = List_IterNext (thread->ids_list);
					List_Del (thread->ids_list, &temp);
					UpdateMaxFD (thread);
				}
				else
				{
					FD_SET (id->Socket.Socket, &readset);
					printf ("THREAD_#%lu FD_SET: id(%ud) > socket (%d)\n", (unsigned long)pthread_self(), id->id, id->Socket.Socket);
					id = List_IterNext (thread->ids_list);
				}
			}
		MUTEX_UNLOCK (thread->ids_mutex);

		tm.tv_sec = 1;
		tm.tv_usec= 0;

		error = select (thread->MaxFds + 1, &readset, NULL, NULL, &tm);
		if (error)
		{
			MUTEX_LOCK(thread->ids_mutex);
			id = List_InitIter (thread->ids_list);
			while (id != NULL)
			{
				Client* clienT = HashTable_Find (clientstb, id->user);
				if (FD_ISSET (id->Socket.Socket, &readset))
				{
					error = RecvFromUser (id->Socket.Socket, buffer, sizeof (buffer)-2);
					if (error == -1)
					{
						perror ("recv()");
						KillClient (id->user, "Recv error"); /*should: closeSocket + del from HashTable*/
					}
					else
					if (error == -2)
					{
						printf ("broken pipe from %s\n", id->user);
						KillClient (id->user, "Broken pipe"); /*should: closeSocket + del from HashTable*/
					}
					else
					{
						printf ("Received buffer: [%s] FROM [%s]\n", buffer, clienT->host);
						if (parseMsg (buffer, &msg) == 0)
						{
							/* process message */
							process_message (id, msg);
						}
						else
						{
							error = SendToClient (id, "Invalid request");
							if (error == -1)
							{
								KillClient (id->user, "Recv error"); /*should: closeSocket + del from HashTable*/
							}
							else
							if (error == -2)
							{
								KillClient (id->user, "Broken pipe"); /*should: closeSocket + del from HashTable*/
							}
							/*else
							{
								id = List_IterNext (thread->ids_list);
							}*/
						}
					}/* if error != -1 && != -2 */
				}/* if (FD_ISSET...) */
				id = List_IterNext (thread->ids_list);
			}/* while (id != NULL) */
			MUTEX_UNLOCK (thread->ids_mutex);
		}
	}
}

void* thread_func_ping (void* arg)
/*
	thread function: for pings
*/
{
	arg = NULL;
	return (NULL);
}

void* thread_func_auth (void* arg)
/*
perform user auth
*/
{
 char nick[NICKSIZE], user[USERSIZE], pass[PASSSIZE], host[HOSTSIZE],
      buffer[BUFFERSIZE];
 int gard;
 Client *client = NULL;
 Peer *peer = NULL;
 fd_set rset;
 struct timeval tm;
 Thread *thread = NULL;

	arg = NULL;

	thread = thread_new (pthread_self());
	if (thread != NULL)
		List_PushTail (threads_list, thread);
	else{
		printf ("Thread got NULL\n");
	}

	while (1){

		if (auth_list->counter != 0){ /* There's more clients to auth */
			MUTEX_LOCK (auth_mutex);
			peer = List_PopHead (auth_list);
			MUTEX_UNLOCK (auth_mutex);
				
			tm.tv_sec = 10;
			tm.tv_usec = 0;

			/*strncpy (host, gethost(peer->sin), HOSTSIZE);*/
			strncpy (host, inet_ntoa (peer->sin.sin_addr), HOSTSIZE);
			printf ("connected host: %s on socket %d\n", host, peer->Socket);
		
			gard = Send (peer->Socket, "-= Aures Open Server =-\r\n");
				if (gard == 0) {
					peer_clear (&peer);
					continue;
				}
				if (gard == -1){
					peer_clear (&peer);
					continue;
				}
		
			FD_ZERO (&rset);
			FD_SET (peer->Socket, &rset);
			gard = select (peer->Socket+1, &rset, NULL, NULL, NULL/*&tm*/);/* waiting for ids for tm.tv_sec seconds */
				if (! FD_ISSET(peer->Socket, &rset)){
					gard = Send (peer->Socket, "timeout\r\n");
					peer_clear (&peer);
					continue;
				}
			
			gard = RecvFromUser(peer->Socket, buffer, BUFFERSIZE);
				if (gard == -1){
						peer_clear (&peer);
						continue;
				}
				if (gard == -2){
						peer_clear (&peer);
						continue;
				}
		
			gard = sscanf (buffer, "%16s %32s %16s", nick, user, pass);
				if (gard != 3){
					gard = send (peer->Socket, "bad request\r\n", 13, 0);
						if (gard == 0) {
							peer_clear (&peer);
							continue;
						}
						if (gard == -1){
							peer_clear (&peer);
							continue;
						}
				}
		
			printf ("NICK %s\nUSER %s\nHOST %s\nPASS %s\n", nick, user, host, pass);
		
			/* ask mysql db */
			/*gard = mysql_Auth (mysql_con, client, user, pass);
				if (gard == -1){
					printf ("mysql error\n");
					close (peer->Socket);
					free (client);
							peertmp = peer;
							peer = peer->next;
							ClearPeer (&peertmp);
					continue;
				}
				else
				if (gard == -2){
					printf ("wrong user/pass\n");
					Send (peer->Socket, "bad User/password\r\n");
					close (peer->Socket);
					free (client);
							peertmp = peer;
							peer = peer->next;
							ClearPeer (&peertmp);
					continue;
				}*/
		
			/* if things goes well: */

			client = client_new (peer->Socket, nick, user, host, USERLEVEL_NONE);
				if (client == NULL){
					printf ("client == NULL!\n");
					peer_clear (&peer);
					continue;
				}

			MUTEX_LOCK(clientinsert_mutex);
			thread_client_insert (client);
			MUTEX_UNLOCK(clientinsert_mutex);

			peer_clear (&peer);

				/* RunHooks(OnAuth) */

		}
		else
		{/* no more clients to auth -- sleep for 500usec */
			while (auth_list->counter == 0){
				tm.tv_usec = 500;
				tm.tv_sec = 0;
				select (0, NULL, NULL, NULL, &tm);
				
			}
		}
	}
}

Thread* thread_get_by_num (int num)
{
	Thread* thread = NULL;
	int nbr = 0;

	LIST_FOREACH (thread, threads_list){
		nbr++;
		if (nbr == num)
			break;
	}

 return thread;
}

void thread_client_insert (Client* client)
{

	Thread *thread = NULL;

	thread = thread_get_by_num (ClientsInsertionOrder);
	ClientsInsertionOrder = THREAD_UPDATE_POS(ClientsInsertionOrder);

	while (thread != NULL)
	{
		if (thread->ids_list->counter < thread->MaxClients)
		{
			HashTable_Insert (clientstb, client, client->user);

			if (client->Socket.Socket > thread->MaxFds){
				thread->MaxFds = client->Socket.Socket;
			}
		
			MUTEX_LOCK(thread->ids_mutex);
			List_PushTail (thread->ids_list, client);
			MUTEX_UNLOCK(thread->ids_mutex);

			break;
		}
		else
		{
			thread = thread_get_by_num (ClientsInsertionOrder);
			ClientsInsertionOrder = THREAD_UPDATE_POS(ClientsInsertionOrder);
		}
	}
}
